﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TiposComuns.Objectos;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Collections;
using System.Net.Sockets;
using TiposComuns.Excepcoes;

namespace PADIbook
{
    /// <summary>
    /// Proxy that encapsulates the details of .net remoting to the 
    /// interface.
    /// </summary>
    public class Cliente
    {
        #region Singleton
        private static readonly Cliente instance = new Cliente();
        public static Cliente GetInstance()
        {
            return instance;
        }

        #endregion

        #region Attributes
        private TcpChannel tcp;         //Tcp channel used in connections
        private IServer server;         //Connection to the primary server
        private State state;            //View of the servers and message id
        private Person person;          //User's addres and username

        public State State
        {
            get { return state; }
            set { state = value; }
        }
        
        #endregion

        #region Methods

        #region Init
        public bool StateOK()
        {
            return this.state.IsOK();
        }

        private void FindServer(int se)
        {
            
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    server = (IServer)Activator.GetObject(typeof(IServer),
                        string.Format("{0}:{1}/Server", this.person.Address.URI,
                        this.person.Address.Port + se));

                    this.state = server.GetState();
                    Console.WriteLine("O cliente inicia com o id " + this.state.IdMensagem);
                    flag = false;
                }
                catch (Exception e)
                {
                    //throw e;
                    se = (se == 3) ? 1 : se + 1;
                }
            }
        }


        private void ReconnectServer(int i)
        {
            try
            {
                server = (IServer)Activator.GetObject(typeof(IServer),
                             string.Format("{0}:{1}/Server", this.person.Address.URI,
                             this.person.Address.Port + this.state.View.ViewState[i] + 1));

                Console.WriteLine("O cliente reconectou-se ao servidor " + (this.person.Address.Port + this.state.View.ViewState[i] + 1));
            }
            catch(Exception e)
            {
                Console.WriteLine("Erro reconnect server: "+e.Message);
            }
        }

        private void ReconnectChange(int i)
        {
            try
            {
                server = (IServer)Activator.GetObject(typeof(IServer),
                             string.Format("{0}:{1}/Server", this.person.Address.URI,
                             this.person.Address.Port + i + 1));

                Console.WriteLine("O cliente reconectou-se ao servidor " + (this.person.Address.Port + this.state.View.ViewState[i] + 1));
            }
            catch (Exception e)
            {
                Console.WriteLine("Erro reconnect server: " + e.Message);
            }
        }

        /// <summary>
        /// Connects to the primary server
        /// </summary>
        /// <param name="username">Username</param>
        /// <param name="address">Address of the client</param>
        /// <param name="port">Port</param>
        /// <param name="server">Primary server</param>
        public bool Connect(string username, string address, int port, int server)
        {            
            if (tcp==null)
            {
                BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
                provider.TypeFilterLevel = TypeFilterLevel.Full;
                IDictionary props = new Hashtable();
                props["port"] = port;
                props["timeout"] = Config.TIMEOUT_PRIM;
                tcp = new TcpChannel(props,null,provider);
                ChannelServices.RegisterChannel(tcp,false);
            }
            
            Address add = new Address();
            add.URI = address;
            add.Port = port;

            this.person = new Person(username, add);
                        
            this.FindServer(server);
            bool res;
            if (this.state == null || this.state.View.ViewState.Count < 2)
            {
                res = false;
            }
            else
            {
                this.server.Connect(username);
                res = true;
            }

            return res;
        }

        
        #endregion

        #region Posts

        /// <summary>
        /// Returns list of posts
        /// </summary>
        /// <returns>List of posts</returns>
        public List<Post> GetPosts()
        {
            List<Post> res = new List<Post>();
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    res = server.GetPosts();
                    flag = false;
                }
                catch (Exception e)
                {
                    if(i==2) this.state.View.ViewState.Clear();
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->"+ e.Message);
                }
            }
            return res;
        }

        /// <summary>
        /// Post a new message
        /// </summary>
        /// <param name="msg">Message</param>
        /// <param name="date">Message date</param>
        public void Post(string msg, DateTime date)
        {
            Post p = new Post();
            p.Message = msg;
            p.Date = date;
            this.state.IdMensagem.isClient = true;
            p.ID = this.state.IdMensagem;//.Clone();
            Console.WriteLine("Faz um post com o id " + this.state.IdMensagem);
            p.User = this.person;

            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    this.state = server.Post(p);
                    if (state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                    }
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }

        /// <summary>
        /// Returns the latest posts
        /// </summary>
        /// <returns></returns>
        public List<Post> RefreshWall()
        {
            KeyValuePair<State, List<Post>> pair = new KeyValuePair<State,List<Post>>();
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    pair = server.RefreshWall();

                    this.state = pair.Key;
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            return pair.Value;
        }

        #endregion

        #region Profile

        /// <summary>
        /// Returns the user's profile
        /// </summary>
        /// <returns>Profile</returns>
        public Profile GetProfile()
        {
            Profile res = new Profile();
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    res = server.GetProfile();
                    flag = false;
                }
                catch (Exception e)
                {
                    //se nao ha servidores então poe a view vazia
                    if (i == 2) this.state.View.ViewState.Clear();
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            return res;

        }

        public void SetProfile(Profile profile)
        {
            this.state.IdMensagem.isClient = true;
         
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    this.state = server.SetProfile(this.state.IdMensagem, profile);
                    if (this.state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                        Console.WriteLine("Salva o profile com o id " + this.state.IdMensagem);
                    }
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }


        public Address GetAddress()
        {
            return server.GetAddress();
        }
        #endregion

        #region Friends
        public List<Person> GetFriends()
        {
            List<Person> res = new List<Person>();
            
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    res = server.GetFriends();
                    flag = false;
                }
                catch (Exception e)
                {
                    //se nao ha servidores então poe a view vazia
                    if (i == 2) this.state.View.ViewState.Clear();
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            return res;
        }

        public List<Person> GetFriendsReceived()
        {
            List<Person> res = new List<Person>();
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    res = server.GetFriendsReceived();
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            return res;
        }

        public List<Person> GetFriendsSent()
        {
            List<Person> res = new List<Person>();
            
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    res = server.GetFriendsSent();
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            return res;
        }

        public void AddFriend(string username, string address,int port)
        {
            Address add = new Address();
            add.Port = port;
            add.URI = address;
            Person person = new Person(username, add);
            this.state.IdMensagem.isClient = true;
           
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    this.state = server.FriendRequest(this.state.IdMensagem, person);
                    if (this.state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                    }
                    flag = false;
                }
                catch (FriendNotAvailableException e)
                {
                    throw e;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }

        public void AcceptFriend(Person target)
        {
            this.state.IdMensagem.isClient = true;
            
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    this.state = server.FriendAccept(this.state.IdMensagem, target);
                    if (this.state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                    }
                    flag = false;
                }
                catch (FriendNotAvailableException e)
                {
                    throw e;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }

        public void RejectFriend(Person target)
        {
            this.state.IdMensagem.isClient = true;
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                   
                    this.state = server.FriendReject(this.state.IdMensagem, target);
                    if (this.state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                    }
                    flag = false;
                }
                catch (FriendNotAvailableException e)
                {
                    throw e;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        } 

        public void RemoveFriend(Person target)
        {
            this.state.IdMensagem.isClient = true;
           
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    this.state = server.FriendRemove(this.state.IdMensagem, target);
                    if (this.state.IsOK())
                    {
                        //this.state.IdMensagem.Inc();
                    }
                    flag = false;
                }
                catch (FriendNotAvailableException e)
                {
                    throw e;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }
        #endregion

        #region Search
        public void StartLookupUsername(string username)
        {
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    server.StartLookupUsername(username);
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
            
        }

        public void StartLookupInterest(string interest)
        {
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    server.StartLookup(interest);
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }

        public void StartLookupSexAge(int ageMin,int ageMax,bool sex)
        {
            bool flag = true;
            for (int i = 0; flag && i < 3; i++)
            {
                try
                {
                    if (i > 0)
                        ReconnectServer(i);
                    server.StartLookup(ageMin, ageMax, sex);
                    flag = false;
                }
                catch (Exception e)
                {
                    Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
                }
            }
        }

        public KeyValuePair<List<Person>,bool> GetResults()
        {
            try
            {
                return server.GetResults();
            }
            catch (Exception e)
            {
                Console.WriteLine("A pesquisa falhou: " + e.Message);
                throw e;
            }

        }

        #endregion

        public void ChangeServer(int serv)
        {     
            try
            {
                ReconnectChange(serv);
                this.state = server.ChangeServer();
            }
            catch (FriendNotAvailableException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                Console.WriteLine("O primario falhou, vou-me ligar a outra replica. ->" + e.Message);
            }
        }

        #endregion
    }
}
